/*
 Copyright (C) 1996-1997 GX Media, Inc.

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

#include "stdafx.h"
#include "Texture.h"
#include "Game.h"
#include "QDraw.h"

// ===========================================================================
// Texture

Texture::Texture(TexEntry *entry, Game *_game)
{
	cached = false;
	pInfo = NULL;
	tInfo = 0;
	texEntry = entry;
	game = _game;
	realWidth = 0;
	realHeight = 0;
}

Texture::~Texture(void)
{
	if (cached)
		for (int i = 0; i < mips; i++)
			free(mip[i]);

	if (pInfo)
		free(pInfo);
}

wxString Texture::GetFilename(void)
{

	TexDir *texDir = game->GetTexDB()->GetTexDir(texEntry->texDir);
	wxString filename = texDir->dirName;

	if (!texEntry->wadEntry.offset)
	{
		// we're a .wal dir then, so append name
		filename += wxT("/") + texEntry->name + texDir->game->GetTexExt();
	}

	return filename;
}

bool Texture::Cache(void)
{
	if (cached)
		return true;

	if (texEntry->wadEntry.offset == -1)
	{
		//		ASSERT(false);
		return false;
	}

	if (!game->LoadTexture(this, GetFilename(), texEntry->wadEntry.offset))
	{
		//		ASSERT(false);
		return false;
	}

	cached = true;

	// =======================================================
	// force the width and height to be a power of 2
	// (all the QDraw render libraries require this)
	// texture surface is stretched appropiately here
	// divWidth, divHeight keep track of stretch ratio
	// width, height keep track of memory dimensions
	// realWidth, realHeight keep track of real dimensions
	width = NearestGreaterPower2(realWidth);
	height = NearestGreaterPower2(realHeight);

	divWidth = (float) realWidth / (float) width;
	divHeight = (float) realHeight / (float) height;

	if (width != realWidth || height != realHeight)
	{
		int m, w, h, rw, rh, x, y;
		void *oldMip, *newMip;

		// we have to stretch all mips
		for (m = 0; m < mips; m++)
		{
			w = width / (1 << m);
			h = height / (1 << m);
			rw = realWidth / (1 << m);
			rh = realHeight / (1 << m);

			oldMip = mip[m];
			newMip = malloc(w * h * bits / 8);

			if (bits == 8)
			{
				unsigned char *oldPtr;
				unsigned char *newPtr = (unsigned char *) newMip;
				for (y = 0; y < h; y++)
				{
					oldPtr = (unsigned char *) oldMip + (int) ((float) y
							* divHeight) * rw;
					for (x = 0; x < w; x++)
						*newPtr++ = *(oldPtr + (int) ((float) x * divWidth));
				}
			}
			else if (bits == 16)
			{
				unsigned short *oldPtr;
				unsigned short *newPtr = (unsigned short *) newMip;
				for (y = 0; y < h; y++)
				{
					oldPtr = (unsigned short *) oldMip + (int) ((float) y
							* divHeight) * rw;
					for (x = 0; x < w; x++)
						*newPtr++ = *(oldPtr + (int) ((float) x * divWidth));
				}
			}

			free(mip[m]);
			mip[m] = newMip;
		}

		surface = mip[0];
	}
	// =======================================================

	return true;
}

bool Texture::CacheSizes()
{
	if(cached)
		return true;
	if (!game->LoadTextureSizes(this, GetFilename(), texEntry->wadEntry.offset))
		return false;
	return true;
}

wxString Texture::GetShortName(void)
{
	wxString shortName;

	size_t c = texEntry->name.rfind(wxT('/'));
	if (c)
		shortName = texEntry->name.substr(c + 1);
	else
		shortName = texEntry->name;

	c = shortName.rfind(wxT('.'));
	if (c)
		shortName = shortName.substr(0, c);

	return shortName;
}

wxString Texture::GetTexListName(TexDB *texDB)
{
	wxString name;

	TexDir *texDir = texDB->GetTexDir(texEntry->texDir);

	if (texDir->game->UsesWadFile())
	{
		size_t c = texDir->dirName.rfind(wxT('/'));
		if (c)
			name = texDir->dirName.substr(c + 1);
		else
			name = texDir->dirName;
		name += wxT("/");
	}

	name += texEntry->name;

	return name;
}

short *
Texture::GetSolidInfo(void)
{
	if (texEntry->solid[7])
		return texEntry->solid;

	if (!Cache())
	{
		static short tmp[8] =
		{ 0, 0, 0, 0, 0, 0, 0, 0 };
		return tmp;
	}

	// build solid info
	if (bits == 8)
	{
		int i, j, table[256];
		int best, high;
		unsigned char *surf = (unsigned char *) surface;

		for (i = 0; i < 256; i++)
			table[i] = 0;

		for (i = 0; i < width * height; i++)
			table[*surf++]++;

		for (j = 0; j < 7; j++)
		{
			high = 0;
			for (i = 1; i < 256; i++)
				if (table[i] > high)
				{
					best = i;
					high = table[i];
				}
			texEntry->solid[j] = best;
			table[best] = 0;
		}
	}
	else
	{
		int i, j, table[65536];
		int best, high;
		unsigned short *surf = (unsigned short *) surface;

		for (i = 0; i < 65536; i++)
			table[i] = 0;

		for (i = 0; i < width * height; i++)
			table[*surf++]++;

		for (j = 0; j < 7; j++)
		{
			high = 0;
			for (i = 1; i < 65536; i++)
				if (table[i] > high)
				{
					best = i;
					high = table[i];
				}
			texEntry->solid[j] = best;
			table[best] = 0;
		}
	}

	texEntry->solid[7] = 1;

	return texEntry->solid;
}

// ===========================================================================
// TexList

TexList::TexList(const wxString &_filename, Game *game)
{
	filename = _filename;
	texDB = game->GetTexDB();

	LFile file;
	if (!file.Open(filename))
		return;

	while (file.GetNextLine())
	{
		wxString line = wxString(file.GetLine(), wxConvUTF8);
		line.Replace(wxT("\\"), wxT("/"));
		line.Replace(wxT("\r"), wxT(""));
		line.Replace(wxT("\n"), wxT(""));
		Add(line);
	}
}

TexList::~TexList()
{
}

Texture *TexList::Add(const wxString &texName)
{
	Texture *texture = texDB->AddTexture(texName);
	textures.Add(texture);
	return texture;
}

void TexList::Remove(const wxString &texName)
{
	for (int i = 0; i < textures.GetCount(); i++)
	{
		Texture *texture = textures.Item(i);
		if (texName == texture->GetName())
		{
			textures.RemoveAt(i);
			return;
		}
	}
}

void TexList::Save(void)
{
	if (!textures.GetCount())
		return;

	LFile file;

	// Delete the existing file.
	QDraw::OutputText(_("Loading TexList: %s... "), filename.c_str());
	if(LFile::Exist(filename))
		wxRemoveFile(filename);
	else
		QDraw::OutputText(_("Error. Couldn't open TexList.\n"));


	 file.Open(filename, LFILE_WRITE);
	 wxString line;
	 for(size_t i = 0; i < textures.GetCount(); i++)
	 {
		 Texture *texture = textures.Item(i);
		 line.Printf(wxT("%s\n"), texture->GetTexListName(texDB).c_str());
		 const wxCharBuffer &buffer = line.utf8_str();
		 file.Write(buffer, strlen(buffer));
	 }
	 QDraw::OutputText(_("OK.\n"));
}

// ===========================================================================
// TexDB

TexDB::TexDB(Game *_game)
{
	game = _game;

	int i;
	int ver;

	filename = wxString::Format(wxT("%s/texlists/%s.db"),
			LFile::GetInitDir().c_str(), game->GetName().c_str());

	texDirs = 0;
	tryExtract = true;

	LFile file;
	QDraw::OutputText(_("Loading textures database: %s ... "), filename.c_str());
	if (!file.Open(filename))
	{
		QDraw::OutputText(_("Error. Couldn't open the database.\n"));
		return;
	}

	file.Read(&ver, sizeof(ver));
	if (ver != TEXDB_VER)
	{
		QDraw::OutputText(_("Error.\nDB version is %d, should be %d.\n"), ver,
				TEXDB_VER);
		return;
	}

	short numEntries;
	file.Read(&numEntries, sizeof(numEntries));
	texEntries.reserve(numEntries);
	size_t bufferSize = 0;
	char *buffer = NULL;
	for (short i = 0; i < numEntries; i++)
	{
		TexEntry *entry = new TexEntry;
		file.Read(&entry->wadEntry, sizeof(entry->wadEntry));

		short entryNameLen;
		file.Read(&entryNameLen, sizeof(entryNameLen));
		if (entryNameLen + 1 > bufferSize)
		{
			if (buffer)
				delete[] buffer;
			bufferSize = entryNameLen + 1;
			buffer = new char[bufferSize];
		}
		file.Read(buffer, entryNameLen);
		buffer[entryNameLen] = 0;
		entry->name = wxString(buffer, wxConvUTF8);

		file.Read(entry->solid, sizeof(entry->solid));
		file.Read(&entry->texDir, sizeof(entry->texDir));
		file.Read(&entry->used, sizeof(entry->used));

		// Store the readed entry.
		texEntries.push_back(entry);
	}

	file.Read(&texDirs, sizeof(texDirs));
	texDirs = Min(texDirs, TEXDIR_MAX);

	for (i = 0; i < texDirs; i++)
	{
		// Read the dir name.
		short dirNameLen = 0;
		file.Read(&dirNameLen, sizeof(dirNameLen));
		if (dirNameLen + 1 > bufferSize)
		{
			if (buffer)
				delete[] buffer;
			bufferSize = dirNameLen + 1;
			buffer = new char[bufferSize];
		}
		file.Read(buffer, dirNameLen);
		buffer[dirNameLen] = 0;

		texDir[i].dirName = wxString(buffer, wxConvUTF8);

		// Read the game name
		short gameNameLen = 0;
		file.Read(&gameNameLen, sizeof(gameNameLen));
		if (gameNameLen + 1 > bufferSize)
		{
			if (buffer)
				delete[] buffer;
			bufferSize = gameNameLen + 1;
			buffer = new char[bufferSize];
		}
		file.Read(buffer, gameNameLen);
		buffer[gameNameLen] = 0;

		texDir[i].gameName = wxString(buffer, wxConvUTF8);
	}

	if (buffer)
		delete[] buffer;

	for (i = 0; i < texDirs; i++)
		texDir[i].game = game;

	for (i = 0; i < texEntries.size(); i++)
		textures.push_back(new Texture(texEntries[i], game));

	QDraw::OutputText(_("OK.\n"));
}

TexDB::~TexDB()
{
	LFile file;
	LFile::MakeDir(LFile::GetInitDir() + wxT("/texlists/"));
	if (!file.Open(filename, LFILE_WRITE))
		return;

	int ver = TEXDB_VER;

	file.Write(&ver, sizeof(ver));
	short numEntries = texEntries.size();
	file.Write(&numEntries, sizeof(numEntries));
	for (short i = 0; i < numEntries; i++)
	{
		TexEntry *entry = texEntries[i];
		const wxCharBuffer &entryBuffer = entry->name.utf8_str();

		file.Write(&entry->wadEntry, sizeof(entry->wadEntry));

		short entryNameLen = strlen(entryBuffer.data());
		file.Write(&entryNameLen, sizeof(entryNameLen));
		file.Write(entryBuffer.data(), entryNameLen);

		file.Write(entry->solid, sizeof(entry->solid));
		file.Write(&entry->texDir, sizeof(entry->texDir));
		file.Write(&entry->used, sizeof(entry->used));
	}

	file.Write(&texDirs, sizeof(texDirs));

	for (int i = 0; i < texDirs; i++)
	{
		const wxCharBuffer &dirBuffer = texDir[i].dirName.utf8_str();
		const wxCharBuffer &gameBuffer = texDir[i].gameName.utf8_str();

		short dirLen = strlen(dirBuffer.data());
		file.Write(&dirLen, sizeof(dirLen));
		file.Write(dirBuffer.data(), dirLen);

		short gameLen = strlen(gameBuffer.data());
		file.Write(&gameLen, sizeof(gameLen));
		file.Write(gameBuffer.data(), gameLen);
	}

	for (int i = 0; i < texEntries.size(); i++)
		delete texEntries[i];
	for (int i = 0; i < textures.size(); i++)
		delete textures[i];
}

Texture *
TexDB::AddTexture(const wxString &name)
{
	// Qualified texture search system is very different.
	if(game->UsesQualifiedTextures())
		return AddTextureQualified(name);

	wxString fullName;
	wxString texName;
	wxString dirName;
	size_t pos;

	fullName = name;
	fullName.Replace(wxT("\\"), wxT("/"));

	pos = fullName.rfind(wxT('/'));
	if (pos != wxString::npos)
	{
		dirName = fullName.substr(0, pos);
		texName = fullName.substr(pos + 1);
	}
	else
	{
		texName = fullName;
		dirName = wxT("");
	}

	// strip .wal from texName
	if (!game->UsesWadFile())
	{
		pos = texName.rfind(wxT('.'));
		if (pos != wxString::npos)
		{
			texName = texName.substr(0, pos);
		}
	}

	Texture *tex = FindTexture(texName, false);
	if (tex)
		return tex;

	int texDir = FindTexDir(dirName);
	WadEntry wadEntry;

	if (texDir == -1)
	{
		//		if(strlen(dirName) && (LFile::Exist(dirName) || LFile::ExistDir(dirName)))
		if (!dirName.empty() && dirName.rfind(wxT('/')) != wxString::npos)
			texDir = AddTexDir(dirName);
		else
		{
			wxString oldName = dirName;
			dirName = game->GetTexDir();
			if (!oldName.empty())
				dirName += wxT("/");
			dirName += oldName;

			if (LFile::Exist(dirName) || LFile::ExistDir(dirName))
				texDir = AddTexDir(dirName);
		}
	}

	int result = -1;
	wxString walName;
	if (texDir != -1)
	{
		result = GetWadEntry(dirName, texName, &wadEntry);
		if (result == -1)
		{
			walName = texName + game->GetTexExt();
			result = GetWadEntry(dirName, walName, &wadEntry);
		}
	}

	/*
	 if(result == -1 && tryExtract) {
	 game->ExtractTextures();
	 result = GetWadEntry(dirName, texName, &wadEntry);
	 if(result == -1)
	 result = GetWadEntry(dirName, walName, &wadEntry);
	 }
	 */

	if (result == -1)
	{
		TRACE(wxT("TexDB::AddTexture: couldn't find texture '%s'"), fullName.c_str());
		static TexEntry notFoundEntry;
		static Texture notFoundTex(&notFoundEntry, game);
		strcpy(notFoundEntry.wadEntry.name, "NotFound");
		notFoundEntry.wadEntry.offset = -1;
		return &notFoundTex;
	}

	TexEntry *entry = new TexEntry;
	texEntries.push_back(entry);

	if (result > 0)
		entry->name = wxString(wadEntry.name, wxConvUTF8);
	else
	{
		if (texName.size() < 16)
			strcpy(wadEntry.name, texName.utf8_str());
		else
		{
			strncpy(wadEntry.name, texName.utf8_str(), 15);
			wadEntry.name[15] = '\0';
		}
		entry->name = texName;
	}
	entry->solid[7] = 0;
	entry->wadEntry = wadEntry;
	entry->texDir = texDir;

	tex = new Texture(entry, game);
	textures.push_back(tex);
	tex->texEntry = entry;

	return tex;
}

Texture *TexDB::AddTextureQualified(const wxString &name)
{
	// Find if the texure was already added.
	Texture *tex = FindTexture(name, false);
	if (tex)
		return tex;

	// Find it in each tex dir.
	for(int i = 0; i < texDirs; i++)
	{
		TexDir &dir = texDir[i];
		wxString filename = dir.dirName + wxT("/") + name + game->GetTexExt();
		if(!LFile::Exist(filename))
			continue;

		// Found the texture.
		TexEntry *entry = new TexEntry();
		texEntries.push_back(entry);

		entry->name = name;
		entry->solid[7] = 0;
		entry->wadEntry.offset = 0;
		entry->texDir = i;
		strcpy(entry->wadEntry.name, "Qualified");

		// Create the texture.
		tex = new Texture(entry, game);
		textures.push_back(tex);
		tex->texEntry = entry;
		return tex;
	}

	// Couldn't find the texture.
	TRACE(wxT("TexDB::AddTexture: couldn't find texture '%s'"), name.c_str());
	static TexEntry notFoundEntry;
	static Texture notFoundTex(&notFoundEntry, game);
	strcpy(notFoundEntry.wadEntry.name, "NotFound");
	notFoundEntry.wadEntry.offset = -1;
	return &notFoundTex;
}

Texture *
TexDB::FindTexture(const wxString &name, bool iterate)
{
	static bool adding = false;
	if (adding)
		return NULL;

	if (name.empty())
		return NULL;

	int i;

	for (size_t i = 0; i < texEntries.size(); i++)
		if (game == GetTexDir(texEntries[i]->texDir)->game && name.CmpNoCase(
				texEntries[i]->name) == 0)
			return textures[i];

	if (!iterate)
		return NULL;

	// iterate through texdirs
	for (i = 0; i < texDirs; i++)
	{
		wxString checkName = texDir[i].dirName + wxT("/") + name;

		adding = true;
		Texture *texture = AddTexture(checkName);
		adding = false;

		if (texture && texture->texEntry->wadEntry.offset != -1)
			return texture;
	}

	return NULL;
}

int TexDB::AddTexDir(const wxString &name)
{
	// Don't add empty texture dirs.
	if(name.empty())
		return -1;

	int val = FindTexDir(name);
	if (val != -1)
		return val;

	texDir[texDirs].dirName = name;
	texDir[texDirs].gameName = game->GetName();
	texDir[texDirs].game = game;

	return texDirs++;
}

int TexDB::FindTexDir(const wxString &name)
{
	for (int i = 0; i < texDirs; i++)
		if (!texDir[i].dirName.empty() && name == texDir[i].dirName)
			return i;

	return -1;
}

void TexDB::DelTexDir(TexDir *texDir)
{
	bool found = false;
	for (int i = 0; i < texDirs; i++)
	{
		if (texDir == &texDir[i])
			found = true;
		if (found && i + 1 < texDirs)
			texDir[i] = texDir[i + 1];
	}
	texDirs--;
}

int TexDB::GetWadEntry(const wxString &dirName, const wxString &texName,
		WadEntry *rtnWadEntry)
{
	if (LFile::ExistDir(dirName))
	{
		wxString filename = dirName + wxT("/") + texName;
		if (LFile::Exist(filename))
		{
			rtnWadEntry->offset = 0;
			return 0;
		}
		else
			return -1;
	}
	else
	{
		WadHeader wadHeader;
		WadEntry *wadEntry;

		LFile file;
		if (!file.Open(dirName))
			return -1;

		file.Read(&wadHeader, sizeof(WadHeader));
		if (strncmp(wadHeader.magic, "WAD2", 4) && strncmp(wadHeader.magic,
				"WAD3", 4))
			return -1;

		wadEntry = new WadEntry[wadHeader.entries];
		file.Seek(wadHeader.offset);
		file.Read(wadEntry, sizeof(WadEntry), wadHeader.entries);

		int rtn = -1;
		for (int i = 0; i < wadHeader.entries; i++)
			if (texName == wxString(wadEntry[i].name, wxConvUTF8))
			{
				*rtnWadEntry = wadEntry[i];
				rtn = wadEntry[i].offset;
				break;
			}

		delete[] wadEntry;

		return rtn;
	}
}

void TexDB::ResetUsed(void)
{
	for (size_t i = 0; i < texEntries.size(); i++)
		texEntries[i]->used = false;
}

void TexDB::BuildWadFromUsed(const wxString &filename)
{
	int i, c;

	WadHeader wadHeader;
	wadHeader.entries = 0;

	for (size_t i = 0; i < texEntries.size(); i++)
		if (texEntries[i]->used)
			wadHeader.entries++;

	ASSERT(NULL);
	//CProgressWnd progressWnd(AfxGetMainWnd(), "Progress");
	//progressWnd.NoCancelButton();

	char building[256];
	sprintf(building, "Building %s", (const char*) filename.utf8_str());
	//progressWnd.SetText(building);

	WadEntry *wadEntry = new WadEntry[wadHeader.entries];

	// hack!!
	if (game->GetName() == wxT("Half-Life"))
		strncpy(wadHeader.magic, "WAD3", 4);
	else
		strncpy(wadHeader.magic, "WAD2", 4);

	wadHeader.offset = sizeof(WadHeader);

	LFile outFile;
	outFile.Open(filename, LFILE_WRITE);
	outFile.Seek(wadHeader.offset);

	char qwad[4];
	strncpy(qwad, "QWAD", 4);
	outFile.Write(qwad, 4);
	wadHeader.offset += 4;

	c = 0;
	for (size_t i = 0; i < texEntries.size(); i++)
	{
		if (!texEntries[i]->used)
			continue;

		int size = texEntries[i]->wadEntry.dsize;

		LFile inFile;
		inFile.Open(textures[i]->GetFilename());
		inFile.Seek(texEntries[i]->wadEntry.offset);

		char *buf = (char *) malloc(size);
		inFile.Read(buf, size);
		outFile.Write(buf, size);
		free(buf);

		wadEntry[c] = texEntries[i]->wadEntry;
		wadEntry[c].offset = wadHeader.offset;
		wadHeader.offset += size;

		//progressWnd.SetPos(c * 100 / wadHeader.entries);
		c++;
	}

	outFile.Write(wadEntry, sizeof(WadEntry), wadHeader.entries);
	outFile.Seek(0);
	outFile.Write(&wadHeader, sizeof(WadHeader));
}

wxString TexDB::BuildWadListFromUsed(void)
{
	wxString list = wxT("");

	bool dirUsed[TEXDIR_MAX];
	int i;

	for (i = 0; i < texDirs; i++)
		dirUsed[i] = false;

	for (size_t i = 0; i < texEntries.size(); i++)
		if (texEntries[i]->used)
			dirUsed[texEntries[i]->texDir] = true;

	for (i = 0; i < texDirs; i++)
	{
		if (dirUsed[i])
		{
			if (!list.empty())
				list += wxT(";");
			list += wxString(texDir[i].dirName, wxConvUTF8);
		}
	}

	return list;
}
